#include "PPlayerInfo.h"
#include "sdk.h"
#include "misc.h"
#include <algorithm>
#include "stats.h"
#include <cmath>
#include <iostream>

using namespace UnitTypeNamespace;
using namespace OrderTypeNamespace;
using Pos = SDK::Pos;
using PPlayerInfo = SDK::PPlayerInfo;
using PCommand = SDK::PCommand;
using PShowInfo = SDK::PShowInfo;
using Edge = SDK::PPlayerInfo::Edge;
using Robot = SDK::PPlayerInfo::Robot;
using HumanOrder = SDK::PPlayerInfo::HumanOrder;
using Buff = SDK::PPlayerInfo::Robot::Buff;
using Skill = SDK::PPlayerInfo::Robot::Skill;
using Operation = SDK::PCommand::Operation;
using ShowInfo = SDK::PShowInfo::ShowInfo;
namespace SDK {
    Pos::Pos(int x_, int y_) : x(x_), y(y_) {};

    bool Pos::operator==(const Pos &p) const { return x == p.x && y == p.y; }

    bool Pos::operator!=(const Pos &p) const { return x != p.x || y != p.y; }

    bool Pos::operator<(const Pos &p) const { return x < p.x || (x == p.x && y < p.y); }

    Pos Pos::operator+(const Pos &p) const { return Pos(x + p.x, y + p.y);}

    Pos Pos::operator-(const Pos &p) const { return Pos(x - p.x, y - p.y);}

    int Pos::len2() const { return x * x + y * y;}

    double Pos::lenf() const { return sqrt(len2());}
	
	int Pos::dist2(const Pos &p) const { return (*this - p).len2();}
	
	double Pos::distf(const Pos &p) const { return (*this - p).len2();}

    string Pos::ToString() const { return string("(") + to_string(x) + string(",") + to_string(y) + string(")"); }

    Buff *Robot::findBuff(BuffType type) {
        for (unsigned int i = 0; i < buffs.size(); i++)
            if (buffs[i].type == type)
                return &buffs[i];
        return NULL;
    }

    Skill *Robot::findSkill(SkillType type) {
        for (unsigned int i = 0; i < skills.size(); i++)
		{
            if (skills[i].type == type)
                return &skills[i];
		}
        return NULL;
    }

    bool Robot::canUse(SkillType type) {
		if(type == SplitSkill || type == EvolveSkill || type == VestigialSkill || type == ConnectSkill || type == DisconnectSkill || type == TransferSkill)
			return true;
        Skill *skill = this->findSkill(type);
		if(!skill)
			return false;
		if(skill->cd > 0)
			return false;
		return true;
    }

    void PCommand::AddOrder(OrderType orderType, Pos source, Pos target, int energy) {
        Operation op;
        op.type = orderType;
        op.source = source;
        op.target = target;
        op.energy = energy;
        cmds.push_back(op);
    }

    void PCommand::AddOrder(OrderType orderType, Pos source, RobotType robotType /*=RemainSame*/) {
        Operation op;
        op.type = orderType;
        op.source = source;
        op.targetType = robotType;
        op.RemainSame=false;
        cmds.push_back(op);
    }

    void PCommand::Evolve(Pos source) {
        Operation op;
        op.type = OrderType::Evolve;
        op.source = source;
        op.RemainSame=true;
        cmds.push_back(op);
    }

    void PCommand::Vestigial(Pos source) {
        Operation op;
        op.type = OrderType::Vestigial;
        op.source = source;
        op.RemainSame=true;
        cmds.push_back(op);
    }

    SDK::Network PCommand::net;

    bool PCommand::Satisfiable(const PPlayerInfo &info, const map<Pos, int> &needs) {
        std::vector<Pos> poss;
        poss.push_back(Pos(-1, -1));
        //poss.push_back(target);
        for (auto edge: info.edges)
            if (!edge.LeftTime) {
                poss.push_back(edge.source);
                poss.push_back(edge.target);
            }
        std::sort(poss.begin(), poss.end());
        poss.erase(std::unique(poss.begin(), poss.end()), poss.end());
        int n = poss.size();
        int S = n, T = n + 1;
        net.init(n + 1);
        for (int i = 1; i < n; i++)
            net.addEdge(S, i, info.RobotAt(poss[i])->energy);
        int tot = 0;
        for (auto pair: needs) {
            net.addEdge(std::lower_bound(poss.begin(), poss.end(), pair.first) - poss.begin(), T, pair.second);
            tot += pair.second;
        }
        for (auto edge: info.edges)
            if (!edge.LeftTime) {
                int sid = std::lower_bound(poss.begin(), poss.end(), edge.source) - poss.begin();
                int tid = std::lower_bound(poss.begin(), poss.end(), edge.target) - poss.begin();
                int w = info.RobotAt(edge.source)->transport_capacity;
                net.addEdge(sid, tid, w);
            }
        return tot == net.sap(S, T);
    }

    int PCommand::AddAutoTransfer(const PPlayerInfo &info, Pos target, const map<Pos, int> &needs) {
        map<Pos, int> weights;
        weights[target] = 1;
        return this->AddAutoTransfer(info, weights, needs);
        //deprecated
        /*std::vector<Pos> poss;
        poss.push_back(Pos(-1, -1));
        poss.push_back(target);
        for(auto edge: info.edges) if(!edge.LeftTime)
        {
            poss.push_back(edge.source);
            poss.push_back(edge.target);
        }
        std::sort(poss.begin(), poss.end());
        poss.erase(std::unique(poss.begin(), poss.end()), poss.end());
        int n = poss.size();
        net.init(n);
        for(int i = 1; i < n; i++)
            if(target != poss[i])
                net.addEdge(n, i, info.RobotAt(poss[i])->energy);
        for(auto edge: info.edges) if(!edge.LeftTime)
        {
            int sid = std::lower_bound(poss.begin(), poss.end(), edge.source) - poss.begin();
            int tid = std::lower_bound(poss.begin(), poss.end(), edge.target) - poss.begin();
            int w = info.RobotAt(edge.source)->transport_capacity;
            net.addEdge(sid, tid, w);
        }
        int Tid = std::lower_bound(poss.begin(), poss.end(), target) - poss.begin();
        int sap = net.sap(n, Tid);
        // printf("sap %d\n", sap);
        auto flows = net.flows();
        for(auto f: flows)
            if(f.s < n && f.t < n)
                this->AddOrder(Transfer, poss[f.s], poss[f.t], f.w);
        return sap;*/
    }

    int PCommand::AddAutoTransfer(const PPlayerInfo &info, const map<Pos, int> &weights, const map<Pos, int> &needs) {
        // static Network net;
        std::vector<Pos> poss;
        poss.push_back(Pos(-1, -1));
        //for(auto pair: weights)
        //	poss.push_back(pair.first);
        for (auto edge: info.edges)
            if (!edge.LeftTime) {
                poss.push_back(edge.source);
                poss.push_back(edge.target);
            }
        std::sort(poss.begin(), poss.end());
        poss.erase(std::unique(poss.begin(), poss.end()), poss.end());
        double l = 0, r = 1;
        while (_AddAutoTransfer_Helper_Flow(info, weights, r, poss, needs))
            if((r *= 2) >= 1e6)
				goto special;
        while (_AddAutoTransfer_Helper_Sum(weights, l) < _AddAutoTransfer_Helper_Sum(weights, r)) {
            double mid = (l + r) / 2;
            // printf("trans l %lf r %lf mid %lf\n", l, r, mid);
            if (_AddAutoTransfer_Helper_Flow(info, weights, mid, poss, needs))
                l = mid;
            else
                r = mid * (1 - 1e-6);
        }
		special:;
        int n = poss.size();
        _AddAutoTransfer_Helper_Flow(info, weights, std::min(l, r), poss, needs);
        auto flows = net.flows();
        for (auto f: flows)
            if (f.s < n && f.t < n)
                this->AddOrder(Transfer, poss[f.s], poss[f.t], f.w);
        return net.flow;
    }

    bool PCommand::_AddAutoTransfer_Helper_Flow(const PPlayerInfo &info, const map<Pos, int> &weights, double ex,
                                                const std::vector<Pos> &poss, const map<Pos, int> &needs) {
        // printf("flow %lf\n", ex);
        int n = poss.size();
        int S = n, T = n + 1;
        net.init(n + 1);
        for (int i = 1; i < n; i++)
            // if(weights.find(poss[i]) == weights.end())
            net.addEdge(S, i, info.RobotAt(poss[i])->energy);
        int sw = 0;
        for (auto pair: needs) {
            net.addEdge(std::lower_bound(poss.begin(), poss.end(), pair.first) - poss.begin(), T, pair.second);
            sw += pair.second;
        }
        for (auto edge: info.edges)
            if (!edge.LeftTime) {
                int sid = std::lower_bound(poss.begin(), poss.end(), edge.source) - poss.begin();
                int tid = std::lower_bound(poss.begin(), poss.end(), edge.target) - poss.begin();
                int w = info.RobotAt(edge.source)->transport_capacity;
                net.addEdge(sid, tid, w);
            }
        for (auto pair: weights) {
            int tid = std::lower_bound(poss.begin(), poss.end(), pair.first) - poss.begin();
            int cw = ex * pair.second;
            net.addEdge(tid, T, cw);
            sw += cw;
        }
        // printf("helper %lf sw %d\n", ex, sw);
        // printf("sap %lf\n", ex);
		int sap = net.sap(S, T);
        // printf("sapend %lf\n", ex);
        return sap == sw;
    }

    int PCommand::_AddAutoTransfer_Helper_Sum(const map<Pos, int> &weights, double ex) {
        int ans = 0;
        for (auto pair: weights)
            ans += pair.second * ex;
        // printf("helper sum %lf = %d\n", ex, ans);
        return ans;
    }

    void PShowInfo::ShowString(string info) {
        ShowInfo op;
        op.type = ShowInfoType::ShowString;
        op.info = info;;
        cmds.push_back(op);
    }

    void PShowInfo::ShowCircle(Pos center, int radius) {
        ShowInfo op;
        op.type = ShowInfoType::ShowCircle;
        op.target = center;
        op.radius = radius;
        cmds.push_back(op);
    }

    void PShowInfo::PosFlash(Pos target) {
        ShowInfo op;
        op.type = ShowInfoType::PosFlash;
        op.target = target;
        cmds.push_back(op);
    }

    void PShowInfo::SelectGrid(Pos target){
        ShowInfo op;
        op.type = ShowInfoType::SelectGrid;
        op.target = target;
        cmds.push_back(op);
    }

    void PShowInfo::DeselectGrid(){
        ShowInfo op;
        op.type = ShowInfoType::DeselectGrid;
        cmds.push_back(op);
    }

    void PShowInfo::Surrender(){
        ShowInfo op;
        op.type = ShowInfoType::Surrender;
        cmds.push_back(op);
    }

    void PShowInfo::merge(PShowInfo &pShowInfo) {
        for (auto it: pShowInfo.cmds)
            cmds.push_back(it);
    }

    Json::Value PShowInfo::ShowInfo::toJson() {
        Json::Value root;
        if (type == ShowInfoType::ShowString){
            root["type"] = "ShowString";
            root["info"] = info;
        }
        else if (type == ShowInfoType::ShowCircle){
            root["type"] = "ShowCircle";
            root["target"] = target.ToString();
            root["radius"] = radius;
        }
        else if (type == ShowInfoType::PosFlash) {
            root["type"] = "PosFlash";
            root["target"] = target.ToString();
        }
        else if (type == ShowInfoType::SelectGrid) {
            root["type"] = "SelectGrid";
            root["target"] = target.ToString();
        }
        else if (type == ShowInfoType::DeselectGrid) {
            root["type"] = "DeselectGrid";
        }
        else if (type == ShowInfoType::Surrender) {
            root["type"] = "Surrender";
        }
        return root;
    }

    Json::Value PShowInfo::toJson() {
        Json::Value root(Json::arrayValue);
        for (auto i: cmds){
            root.append(i.toJson());
        }
        return root;
    }

    int Skill::Cost(SkillType skillType, int robotLevel, RobotType robotType) {
        int result = 0;
        switch (skillType) {
			case SkillType::PlainAttackSkill :
				result = 0; // 看技能代码好像没扣能量？
				break;
			case SkillType::SplitSkill :
				result = FixedSplitCost;
				break;
			case SkillType::RecoverSkill :
				result = RecoverCost;
				break;
            case SkillType::ShieldingSkill :
                result = ShieldingCost;
                break;
            case SkillType::AirborneSkill :
                result = FixedAirborneCost;
                break;
            case SkillType::OverloadSkill :
                result = OverloadCost;
                break;
            case SkillType::ScourgeSkill :
                result = 0; // 看技能代码好像没扣能量？
                break;
            case SkillType::BombingSkill :
                result = 0; // 看技能代码好像没扣能量？
                break;
            case SkillType::EvolveSkill:
                result = stats::getLevelUPConsumption(robotLevel + 1) - stats::getLevelUPConsumption(robotLevel);
                break;
            case SkillType::VestigialSkill:
                result = stats::getLevelUPConsumption(robotLevel) - stats::getLevelUPConsumption(robotLevel - 1);
                result = int(result * VestigialLost + EPS);
                result = - result;
                break;
            case SkillType::ConnectSkill:
                result = 0;
                break;
			default :
				result = 2333333;
                //TODO:此处应补充全部花费的技能
				//进化退化之类的
                //一定记得要break
        }
        return result;
    }
	
	vector<Edge> PPlayerInfo::edgesFrom(Pos pos, bool withUnfinished) const {
		vector<Edge> ans;
		for(Edge e: edges)
			if(e.source == pos && (withUnfinished || !e.LeftTime))
				ans.push_back(e);
		return ans;
	}
	
	vector<Edge> PPlayerInfo::edgesTo(Pos pos, bool withUnfinished) const {
		vector<Edge> ans;
		for(Edge e: edges)
			if(e.target == pos && (withUnfinished || !e.LeftTime))
				ans.push_back(e);
		return ans;
	}

    const Robot *PPlayerInfo::RobotAt(Pos pos) const {
        for (unsigned int i = 0; i < robots.size(); i++)
            if (robots[i].pos == pos)
                return &robots[i];
        return NULL;
    }

    const Robot *PPlayerInfo::RobotNamed(std::string id) const {
        for (unsigned int i = 0; i < robots.size(); i++)
            if (robots[i].id == id)
                return &robots[i];
        return NULL;
    }


    HumanOrder PPlayerInfo::HumanOrder::fromJson(Json::Value root) {
        HumanOrder humanOrder;
        if (root["type"].asString() == "button"){
            humanOrder.id = root["buttonId"].asInt();
            humanOrder.info = root["name"].asString();
            if (root["isGlobal"].asBool() == true) {
                humanOrder.type = HumanOrderType::GlobalButtonClick;
            }
            else {
                humanOrder.type = HumanOrderType::LocalButtonClick;
                humanOrder.target = Pos(root["targetX"].asInt(), root["targetY"].asInt());
            }
        }
        else if (root["type"].asString() == "mouseclick"){
            if (root["mouseButton"].asString() == "left")
                humanOrder.type = HumanOrderType::LeftMouseClick;
            else if (root["mouseButton"].asString() == "right")
                humanOrder.type = HumanOrderType::RightMouseClick;
            humanOrder.target = Pos(root["targetX"].asInt(), root["targetY"].asInt());
        }
        else if (root["type"].asString() == "mousedown"){
            if (root["mouseButton"].asString() == "left")
                humanOrder.type = HumanOrderType::LeftMouseDown;
            else if (root["mouseButton"].asString() == "right")
                /*暂无right humanOrder.type = HumanOrderType::RightMouseClick*/;
            humanOrder.target = Pos(root["targetX"].asInt(), root["targetY"].asInt());
        }
        else if (root["type"].asString() == "mouseup"){
            if (root["mouseButton"].asString() == "left")
                humanOrder.type = HumanOrderType::LeftMouseUp;
            else if (root["mouseButton"].asString() == "right")
                /*暂无right humanOrder.type = HumanOrderType::RightMouseClick*/;
            humanOrder.target = Pos(root["targetX"].asInt(), root["targetY"].asInt());
        }
        else if (root["type"].asString() == "command"){
            humanOrder.info = root["info"].asString();
            humanOrder.type = HumanOrderType::Console;
        }
        else if (root["type"].asString() == "keydown"){
            humanOrder.info = root["info"].asString();
            humanOrder.id = root["id"].asInt();
            humanOrder.type = HumanOrderType::KeyDown;
        }
        else if (root["type"].asString() == "keyup"){
            humanOrder.info = root["info"].asString();
            humanOrder.id = root["id"].asInt();
            humanOrder.type = HumanOrderType::KeyUp;
        }
        return humanOrder;
    }

    vector<HumanOrder> PPlayerInfo::readHumanOrders(vector<string> orders) {
        vector<HumanOrder> humanOrders;
        for (int i = 0; i < orders.size(); ++ i){
            HumanOrder order = HumanOrder::fromJson(stringToJson(orders[i]));
            humanOrders.push_back(order);
        }
        return humanOrders;
    }

}